msg_tool\scripts\softpal\img\pgd/
base.rs

1use crate::ext::io::*;
2use crate::ext::vec::*;
3use crate::types::*;
4use crate::utils::encoding::*;
5use crate::utils::img::*;
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use msg_tool_macro::*;
9use std::io::{Read, Seek, SeekFrom, Write};
10
11#[derive(Clone, Debug, StructPack, StructUnpack)]
12pub struct PgdGeHeader {
13    pub offset_x: u32,
14    pub offset_y: u32,
15    pub width: u32,
16    pub height: u32,
17    pub canvas_width: u32,
18    pub canvas_height: u32,
19    pub mode: u16,
20    // Some background image has this (0xF)
21    pub _unk: u16,
22}
23
24impl PgdGeHeader {
25    pub fn is_base_file(&self) -> bool {
26        self.offset_x == 0
27            && self.offset_y == 0
28            && self.width == self.canvas_width
29            && self.height == self.canvas_height
30    }
31}
32
33#[derive(Clone, Debug, StructPack, StructUnpack)]
34pub struct PgdDiffHeader {
35    pub offset_x: u16,
36    pub offset_y: u16,
37    pub width: u16,
38    pub height: u16,
39    pub bpp: u16,
40    #[fstring = 0x22]
41    pub base_name: String,
42}
43
44pub struct PgdReader<T: Read + Seek> {
45    pub input: T,
46    output: Vec<u8>,
47    width: u32,
48    height: u32,
49    bpp: u8,
50    method: u16,
51    format: Option<ImageColorType>,
52}
53
54impl<T: Read + Seek> PgdReader<T> {
55    pub fn new(mut input: T, pos: u64) -> Result<Self> {
56        input.seek(SeekFrom::Start(pos))?;
57        let unpacked_size = input.read_u32()?;
58        input.read_u32()?; // packed size
59        let output = vec![0u8; unpacked_size as usize];
60        Ok(PgdReader {
61            input,
62            output,
63            width: 0,
64            height: 0,
65            bpp: 0,
66            method: 0,
67            format: None,
68        })
69    }
70
71    pub fn with_ge_header(input: T, header: &PgdGeHeader) -> Result<Self> {
72        let mut s = Self::new(input, 0x20)?;
73        s.width = header.width;
74        s.height = header.height;
75        s.method = header.mode;
76        Ok(s)
77    }
78
79    pub fn with_diff_header(input: T, header: &PgdDiffHeader) -> Result<Self> {
80        let mut s = Self::new(input, 0x30)?;
81        s.width = header.width as u32;
82        s.height = header.height as u32;
83        s.bpp = header.bpp as u8;
84        Ok(s)
85    }
86
87    pub fn unpack_ge(mut self) -> Result<ImageData> {
88        self.unpack_ge_pre()?;
89        let data = match self.method {
90            1 => self.post_process1()?,
91            2 => self.post_process2()?,
92            3 => self.post_process3()?,
93            _ => return Err(anyhow::anyhow!("Unsupported GE mode: {}", self.method)),
94        };
95        let color_type = self
96            .format
97            .ok_or_else(|| anyhow::anyhow!("Unknown image format"))?;
98        Ok(ImageData {
99            width: self.width,
100            height: self.height,
101            color_type,
102            depth: 8,
103            data,
104        })
105    }
106
107    fn unpack_ge_pre(&mut self) -> Result<()> {
108        let mut dst = 0;
109        let mut ctl = 2;
110        let len = self.output.len();
111        while dst < len {
112            ctl >>= 1;
113            if ctl == 1 {
114                ctl = self.input.read_u8()? as i32 | 0x100;
115            }
116            let mut count;
117            if ctl & 1 != 0 {
118                let mut offset = self.input.read_u16()? as usize;
119                count = offset & 7;
120                if offset & 8 == 0 {
121                    count = count << 8 | (self.input.read_u8()? as usize);
122                }
123                count += 4;
124                offset >>= 4;
125                self.output.copy_overlapped(dst - offset, dst, count);
126            } else {
127                count = self.input.read_u8()? as usize;
128                self.input.read_exact(&mut self.output[dst..dst + count])?;
129            }
130            dst += count;
131        }
132        Ok(())
133    }
134
135    pub fn unpack_overlay(&mut self) -> Result<ImageData> {
136        self.unpack_ge_pre()?;
137        let data = self.post_process_pal(&self.output, 0, self.bpp as usize / 8)?;
138        let color_type = match self.bpp {
139            24 => ImageColorType::Bgr,
140            32 => ImageColorType::Bgra,
141            _ => {
142                return Err(anyhow::anyhow!(
143                    "Unsupported bpp for overlay PGD: {}",
144                    self.bpp
145                ));
146            }
147        };
148        Ok(ImageData {
149            width: self.width,
150            height: self.height,
151            color_type,
152            depth: 8,
153            data,
154        })
155    }
156
157    fn post_process1(&mut self) -> Result<Vec<u8>> {
158        self.format = Some(ImageColorType::Bgra);
159        let input = &self.output;
160        let mut output = Vec::with_capacity(input.len());
161        let plane_size = input.len() / 4;
162        let a_src = 0;
163        let r_src = plane_size;
164        let g_src = plane_size * 2;
165        let b_src = plane_size * 3;
166        for i in 0..plane_size {
167            output.push(input[b_src + i]);
168            output.push(input[g_src + i]);
169            output.push(input[r_src + i]);
170            output.push(input[a_src + i]);
171        }
172        Ok(output)
173    }
174
175    #[inline(always)]
176    fn clamp(v: i32) -> u8 {
177        if v > 255 {
178            255
179        } else if v < 0 {
180            0
181        } else {
182            v as u8
183        }
184    }
185
186    fn post_process2(&mut self) -> Result<Vec<u8>> {
187        self.format = Some(ImageColorType::Bgr);
188        let input = &self.output;
189        let stride = self.width as usize * 3;
190        let segment_size = self.width as usize * self.height as usize / 4;
191        let mut src0 = 0;
192        let mut src1 = segment_size;
193        let mut src2 = segment_size * 2;
194        let mut output = vec![0u8; stride * self.height as usize];
195        let mut dst = 0;
196        let points = [0, 1, self.width, self.width + 1];
197        for _y in (1..=(self.height as usize / 2)).rev() {
198            for _x in (1..=(self.width as usize / 2)).rev() {
199                let i0 = input[src0] as i8;
200                let i1 = input[src1] as i8;
201                let b = 226 * i0 as i32;
202                let g = -43 * i0 as i32 - 89 * i1 as i32;
203                let r = 179 * i1 as i32;
204                src0 += 1;
205                src1 += 1;
206                for i in 0..4 {
207                    let mut offset = points[i] as usize;
208                    let base_value = (input[src2 + offset] as i32) << 7;
209                    offset = dst + 3 * offset;
210                    output[offset] = Self::clamp((base_value + b) >> 7);
211                    output[offset + 1] = Self::clamp((base_value + g) >> 7);
212                    output[offset + 2] = Self::clamp((base_value + r) >> 7);
213                }
214                src2 += 2;
215                dst += 6;
216            }
217            src2 += self.width as usize;
218            dst += stride;
219        }
220        Ok(output)
221    }
222
223    fn post_process3(&mut self) -> Result<Vec<u8>> {
224        let input = &self.output;
225        let reader = MemReaderRef::new(input);
226        let bbp = reader.cpeek_u16_at(0x2)?;
227        self.format = Some(if bbp == 24 {
228            ImageColorType::Bgr
229        } else if bbp == 32 {
230            ImageColorType::Bgra
231        } else {
232            return Err(anyhow::anyhow!("Unsupported bpp: {}", bbp));
233        });
234        self.width = reader.cpeek_u16_at(0x4)? as u32;
235        self.height = reader.cpeek_u16_at(0x6)? as u32;
236        self.post_process_pal(input, 8, bbp as usize / 8)
237    }
238
239    fn post_process_pal(&self, input: &[u8], mut src: usize, pixel_size: usize) -> Result<Vec<u8>> {
240        let stride = self.width as usize * pixel_size;
241        let mut output = vec![0u8; stride * self.height as usize];
242        let mut ctl = src;
243        src += self.height as usize;
244        let mut dst = 0;
245        for _row in 0..self.height as usize {
246            let c = input[ctl];
247            ctl += 1;
248            if c & 1 != 0 {
249                let mut prev = dst;
250                for _ in 0..pixel_size {
251                    output[dst] = input[src];
252                    dst += 1;
253                    src += 1;
254                }
255                let mut count = stride - pixel_size;
256                while count > 0 {
257                    count -= 1;
258                    output[dst] = output[prev].wrapping_sub(input[src]);
259                    dst += 1;
260                    prev += 1;
261                    src += 1;
262                }
263            } else if c & 2 != 0 {
264                let mut prev = dst - stride;
265                let mut count = stride;
266                while count > 0 {
267                    count -= 1;
268                    output[dst] = output[prev].wrapping_sub(input[src]);
269                    dst += 1;
270                    prev += 1;
271                    src += 1;
272                }
273            } else {
274                for _ in 0..pixel_size {
275                    output[dst] = input[src];
276                    dst += 1;
277                    src += 1;
278                }
279                let mut prev = dst - stride;
280                let mut count = stride - pixel_size;
281                while count > 0 {
282                    count -= 1;
283                    output[dst] = (((output[prev] as u16)
284                        .wrapping_add(output[dst - pixel_size] as u16)
285                        / 2) as u8)
286                        .wrapping_sub(input[src]);
287                    dst += 1;
288                    prev += 1;
289                    src += 1;
290                }
291            }
292        }
293        Ok(output)
294    }
295}
296
297pub struct PgdWriter {
298    data: ImageData,
299    method: u32,
300    fake_compress: bool,
301}
302
303impl PgdWriter {
304    pub fn new(data: ImageData, fake_compress: bool) -> Self {
305        Self {
306            data,
307            method: 3,
308            fake_compress,
309        }
310    }
311
312    pub fn with_method(mut self, method: u32) -> Self {
313        self.method = method;
314        self
315    }
316
317    pub fn pack_ge<W: Write>(mut self, mut writer: W) -> Result<()> {
318        let data = match self.method {
319            3 => self.process3()?,
320            _ => panic!("Unsupported GE mode: {}", self.method),
321        };
322        let unpacked_len = data.len() as u32;
323        let compressed = if self.fake_compress {
324            ge_fake_compress(&data)?
325        } else {
326            ge_compress(&data)?
327        };
328        let packed_len = compressed.len() as u32;
329        writer.write_u32(unpacked_len)?;
330        writer.write_u32(packed_len)?;
331        writer.write_all(&compressed)?;
332        Ok(())
333    }
334
335    fn process3(&mut self) -> Result<Vec<u8>> {
336        let bpp = self.data.color_type.bpp(8) as usize;
337        let width = self.data.width as u16;
338        let height = self.data.height as u16;
339        let mut data = MemWriter::new();
340        data.write_u16(0)?; // unk
341        data.write_u16(bpp as u16)?;
342        data.write_u16(width)?;
343        data.write_u16(height)?;
344        data.write_all(&self.process_pal()?)?;
345        Ok(data.into_inner())
346    }
347
348    fn process_pal(&mut self) -> Result<Vec<u8>> {
349        let bpp = match self.data.color_type {
350            ImageColorType::Bgr => 3,
351            ImageColorType::Bgra => 4,
352            ImageColorType::Rgb => {
353                convert_rgb_to_bgr(&mut self.data)?;
354                3
355            }
356            ImageColorType::Rgba => {
357                convert_rgba_to_bgra(&mut self.data)?;
358                4
359            }
360            _ => {
361                return Err(anyhow::anyhow!(
362                    "Unsupported color type for palettized PGD: {:?}",
363                    self.data.color_type
364                ));
365            }
366        };
367        // Fixed mode
368        let ctl = vec![1u8; self.data.height as usize];
369        let stride = self.data.width as usize * bpp;
370        let mut output = vec![0u8; stride * self.data.height as usize];
371        let mut dst = 0;
372        for _ in 0..self.data.height as usize {
373            let mut prev = dst;
374            for _ in 0..bpp {
375                output[dst] = self.data.data[dst];
376                dst += 1;
377            }
378            let mut count = stride - bpp;
379            while count > 0 {
380                count -= 1;
381                output[dst] = self.data.data[prev].wrapping_sub(self.data.data[dst]);
382                dst += 1;
383                prev += 1;
384            }
385        }
386        let mut result = Vec::with_capacity(ctl.len() + output.len());
387        result.extend_from_slice(&ctl);
388        result.extend_from_slice(&output);
389        Ok(result)
390    }
391}
392
393fn ge_fake_compress(data: &[u8]) -> Result<Vec<u8>> {
394    let mut output = Vec::new();
395    let mut pos = 0;
396    let data_len = data.len();
397
398    while pos < data_len {
399        // 每8个数据块需要一个控制字节
400        // 控制字节为0表示接下来8个操作都是直接数据复制
401        output.push(0u8);
402
403        // 处理最多8个数据块
404        for _ in 0..8 {
405            if pos >= data_len {
406                break;
407            }
408
409            // 计算当前块的大小(最大255字节)
410            let chunk_size = std::cmp::min(255, data_len - pos);
411
412            // 写入块大小
413            output.push(chunk_size as u8);
414
415            // 写入数据
416            output.extend_from_slice(&data[pos..pos + chunk_size]);
417
418            pos += chunk_size;
419        }
420    }
421
422    Ok(output)
423}
424
425// 新增:基于散列的快速 LZSS 压缩,兼容 unpack_ge_pre
426fn ge_compress(data: &[u8]) -> Result<Vec<u8>> {
427    const MIN_MATCH: usize = 4;
428    const MAX_LEN: usize = 0x7FF + 4; // 2047 + 4 = 2051
429    const MAX_DIST: usize = 0xFFF; // 12-bit distance
430    const HASH_BITS: usize = 16;
431    const HASH_SIZE: usize = 1 << HASH_BITS;
432
433    #[inline(always)]
434    fn hash3(bytes: &[u8]) -> usize {
435        // 3字节哈希,乘黄金常数,取高 HASH_BITS 位
436        let v = ((bytes[0] as u32) << 16) ^ ((bytes[1] as u32) << 8) ^ (bytes[2] as u32);
437        (v.wrapping_mul(0x9E3779B1) >> (32 - HASH_BITS)) as usize
438    }
439
440    let n = data.len();
441    if n == 0 {
442        return Ok(Vec::new());
443    }
444
445    let mut out = Vec::with_capacity(n / 2 + 16);
446
447    // 控制块状态
448    let mut ctrl_pos = out.len();
449    out.push(0u8); // 占位
450    let mut ctrl: u8 = 0;
451    let mut ctrl_cnt: u8 = 0;
452
453    // 哈希表:保存最近出现位置
454    let mut head = vec![usize::MAX; HASH_SIZE];
455
456    // 延迟字面量缓冲
457    let mut lit_start = 0usize;
458    let mut lit_len = 0usize;
459
460    // 辅助:开始新的控制块
461    #[inline(always)]
462    fn start_block(buf: &mut Vec<u8>, ctrl_pos: &mut usize, ctrl: &mut u8, ctrl_cnt: &mut u8) {
463        *ctrl = 0;
464        *ctrl_cnt = 0;
465        *ctrl_pos = buf.len();
466        buf.push(0u8);
467    }
468
469    // 辅助:写入控制字节
470    #[inline(always)]
471    fn flush_ctrl(buf: &mut Vec<u8>, ctrl_pos: usize, ctrl: u8) {
472        if let Some(slot) = buf.get_mut(ctrl_pos) {
473            *slot = ctrl;
474        }
475    }
476
477    // 辅助:输出字面量(可拆分为多个 <=255 的条目)
478    let flush_literals = |out: &mut Vec<u8>,
479                          ctrl: &mut u8,
480                          ctrl_cnt: &mut u8,
481                          ctrl_pos: &mut usize,
482                          lit_start: &mut usize,
483                          lit_len: &mut usize| {
484        while *lit_len > 0 {
485            if *ctrl_cnt == 8 {
486                flush_ctrl(out, *ctrl_pos, *ctrl);
487                start_block(out, ctrl_pos, ctrl, ctrl_cnt);
488            }
489            let chunk = std::cmp::min(255, *lit_len);
490            out.push(chunk as u8);
491            out.extend_from_slice(&data[*lit_start..*lit_start + chunk]);
492            *lit_start += chunk;
493            *lit_len -= chunk;
494            *ctrl_cnt += 1; // 字面量控制位为0,无需设置位
495        }
496    };
497
498    let mut pos = 0usize;
499
500    while pos < n {
501        // 尝试匹配
502        let mut best_len = 0usize;
503        let mut best_dist = 0usize;
504
505        if pos + MIN_MATCH <= n {
506            let h = hash3(&data[pos..pos + 3]);
507            let cand = head[h];
508            if cand != usize::MAX && cand < pos {
509                let dist = pos - cand;
510                if dist > 0 && dist <= MAX_DIST {
511                    // 计算匹配长度
512                    let max_len = std::cmp::min(MAX_LEN, n - pos);
513                    // 快速比较
514                    let mut l = 0usize;
515                    while l < max_len && data[cand + l] == data[pos + l] {
516                        l += 1;
517                    }
518                    if l >= MIN_MATCH {
519                        best_len = l;
520                        best_dist = dist;
521                    }
522                }
523            }
524        }
525
526        if best_len >= MIN_MATCH {
527            // 先刷新字面量
528            flush_literals(
529                &mut out,
530                &mut ctrl,
531                &mut ctrl_cnt,
532                &mut ctrl_pos,
533                &mut lit_start,
534                &mut lit_len,
535            );
536
537            // 控制块满则换块
538            if ctrl_cnt == 8 {
539                flush_ctrl(&mut out, ctrl_pos, ctrl);
540                start_block(&mut out, &mut ctrl_pos, &mut ctrl, &mut ctrl_cnt);
541            }
542
543            let l = best_len.min(MAX_LEN);
544            let dist = best_dist;
545
546            // 写入回溯条目:u16 (LE),必要时再跟长度低8位
547            let len_minus4 = l - 4;
548            let mut word: u16 = ((dist as u16) << 4) as u16;
549            if len_minus4 <= 7 {
550                // 短长度:bit3=1,低3位为长度
551                word |= (len_minus4 as u16) | 0x8;
552                out.push((word & 0xFF) as u8);
553                out.push((word >> 8) as u8);
554            } else {
555                // 扩展长度:bit3=0,低3位为长度高3位,随后写低8位
556                word |= ((len_minus4 >> 8) as u16) & 0x7;
557                out.push((word & 0xFF) as u8);
558                out.push((word >> 8) as u8);
559                out.push((len_minus4 & 0xFF) as u8);
560            }
561
562            // 设置控制位为1
563            let bit = 1u8.wrapping_shl(ctrl_cnt as u32);
564            ctrl |= bit;
565            ctrl_cnt += 1;
566
567            // 更新哈希表(覆盖匹配范围,避免过度开销也可简化为只更新起始位)
568            let end = pos + l;
569            let upd_end = end
570                .saturating_sub(MIN_MATCH - 1)
571                .min(n.saturating_sub(MIN_MATCH));
572            let mut p = pos;
573            while p <= upd_end {
574                let h = hash3(&data[p..p + 3]);
575                head[h] = p;
576                p += 1;
577            }
578
579            pos += l;
580            lit_start = pos;
581            lit_len = 0;
582        } else {
583            // 作为字面量
584            if pos + 2 < n {
585                let h = hash3(&data[pos..pos + 3]);
586                head[h] = pos;
587            }
588            pos += 1;
589            lit_len += 1;
590
591            // 字面量满255则立刻输出一条
592            if lit_len == 255 {
593                flush_literals(
594                    &mut out,
595                    &mut ctrl,
596                    &mut ctrl_cnt,
597                    &mut ctrl_pos,
598                    &mut lit_start,
599                    &mut lit_len,
600                );
601            }
602        }
603    }
604
605    // 结束时刷新剩余字面量与控制字节
606    if lit_len > 0 {
607        flush_literals(
608            &mut out,
609            &mut ctrl,
610            &mut ctrl_cnt,
611            &mut ctrl_pos,
612            &mut lit_start,
613            &mut lit_len,
614        );
615    }
616    flush_ctrl(&mut out, ctrl_pos, ctrl);
617
618    Ok(out)
619}